/**
 * 
 */
package info.niwota.webagent;

import info.niwota.gae.cache.BlobObject;
import info.niwota.gae.cache.DataStoreUtils;
import info.niwota.gae.cache.UploadService;
import info.niwota.org.json.JSONArray;
import info.niwota.org.json.JSONException;
import info.niwota.org.json.JSONObject;

import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;


/**
 * @author qiangli
 * 
 */
@SuppressWarnings("serial")
public class ProxyServlet extends BaseServlet {
	private static final Logger log = Logger.getLogger(ProxyServlet.class
			.getName());

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			handle(req, resp);
		} catch (Exception e) {
			log.log(Level.SEVERE, e.getMessage());
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			handle(req, resp);
		} catch (Exception e) {
			log.log(Level.SEVERE, e.getMessage());
		}
	}


	private void doLogout(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		UserService userService = UserServiceFactory.getUserService();
		
		String url = req.getHeader("X-Return-Url");
	
		if (isEmpty(url)) {
			url = "/";
		}
		
		resp.sendRedirect(userService.createLogoutURL(url));
	}

	private void doLogin(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		UserService userService = UserServiceFactory.getUserService();

		String url = req.getHeader("X-Return-Url");
	
		if (isEmpty(url)) {
			url = "/" + query(req);
		}
		
		if (userService.isUserLoggedIn()) {
			resp.sendRedirect(url);
			return;
		}
		
        resp.sendRedirect(userService.createLoginURL(url));
	}
	
	private void handle(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		
		log.info(getCurrentUser());
		
		final String uri = req.getRequestURI();
		
		if (uri.startsWith("/test")) {
			forward(req, resp, "/WEB-INF/test.jsp");
			return;
		}
		
		//
		if (uri.startsWith("/login")) {
			doLogin(req, resp);
			return;
		}
		
		if (uri.startsWith("/logout")) {
			doLogout(req, resp);
			return;
		}
		
		if (uri.startsWith("/help")) {
			resp.sendRedirect("/assets/web/help.html");
			return;
		}

		UserService userService = UserServiceFactory.getUserService();
		
		final boolean loggedIn = userService.isUserLoggedIn();
		final User user = userService.getCurrentUser();
		
		String peer = req.getParameter("peer");
		String type = req.getParameter("type");
		
		//
		
		if (peer == null && user == null) {

			doLogin(req, resp);
			return;
		}
		
		final String username = (user == null ? "" : user.getEmail());
		//self
		if (peer == null) {
			peer = username;
		}
		
		//
		if (uri.equals("/")) {
			JSONObject data = new JSONObject();
			
			// To determine access level:
			// master: user is peer
			// shared: logged in user
			// public: otherwise
			
			int access = (username.equals(peer) ? 1 : (loggedIn ? 2 : 3));
			boolean isAdmin = (loggedIn && userService.isUserAdmin());
			
			data.put("peer", peer);
			data.put("authenticated", loggedIn);
			data.put("user", username);
			data.put("access", access);
			data.put("admin", isAdmin);
			//

			req.setAttribute("user", user);
			req.setAttribute("peer", peer);
			req.setAttribute("data", data.toString());
			req.setAttribute("loggedIn", loggedIn);
			
			RequestDispatcher dispatch = req.getRequestDispatcher("/WEB-INF/main.jsp");
			dispatch.forward(req, resp);
			return;
		}
		
		// self if proxy
		if (uri.startsWith("/home")) {
			if (peer.equals("hotwebproxy@appspot.com") || (type != null && type.equals("html"))) {
				resp.sendRedirect("/?type=html&peer=" + peer);
				return;
			}
		}
		
		if (uri.equals("/app")) {
			String pkg = req.getParameter("pkg");
			String link = AppRegistry.getAppLink(pkg);
			if (link != null) {
				resp.sendRedirect(link + query(req));
			}
			return;
		}
		
		if (uri.equals("/peer")) {
			resp.sendRedirect("/?type=html&peer=" + peer);
			return;
		}

		// redirect if found in cache
		BlobObject obj = DataStoreUtils.get(peer, uri);
		
		if (obj != null) {
			String link = createDownloadLink(obj.getBlob());
			resp.setContentType(obj.getType());
			resp.sendRedirect(link);
			return;
		}
		
		// go get it
		String postback = UploadService.createPostLink();
		JSONObject data = new JSONObject();
		
		data.put("postback", postback);
		data.put("session", serialize(req.getSession()));
		data.put("cookies", serialize(req.getCookies()));
		
		sendMessage(req, resp, "GET", data);
	}

	protected String createDownloadLink(String blob) {
		String link = UploadService.createDownloadLink(blob);
		return "http://hotwebproxy.appspot.com" + link;
	}

	private JSONArray serialize(Cookie[] cookies) throws JSONException {
		int size = (cookies == null ? 0 : cookies.length);
		JSONArray ca = new JSONArray();
		for (int i = 0; i < size; i++) {
			Cookie c = cookies[i];
			JSONObject o = new JSONObject();
			o.put("name", c.getName());
			o.put("value", c.getValue());
			//
			ca.put(o);
		}
		return ca;
	}

	private JSONObject serialize(HttpSession session) throws JSONException {
		JSONObject so = new JSONObject();
		if (session != null) {
			so.put("sid", session.getId());
			for (Enumeration<?> en = session.getAttributeNames(); en.hasMoreElements();) {
				String  n = (String) en.nextElement();
				//TODO non string session object?
				so.put(n, session.getAttribute(n).toString());
			}
		}
		return so;
	}
	
}
